package com.origin.oss.operation.upload;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.origin.oss.entity.FileInfo;
import com.origin.oss.entity.FileUploadTask;
import com.origin.oss.entity.ImageInfo;
import com.origin.oss.enums.StorageType;
import com.origin.oss.manager.FileSliceUploadManager;
import com.origin.oss.operation.upload.domain.FileSliceUpload;
import com.origin.oss.specification.domain.multipart.response.CompleteSliceUploadResp;
import com.origin.oss.operation.upload.request.QiwenMultipartFile;
import com.origin.oss.exception.operation.UploadException;
import com.origin.oss.service.FileInfoService;
import com.origin.oss.service.FileUploadTaskService;
import com.origin.oss.service.ImageInfoService;
import com.origin.oss.util.concurrent.locks.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.multipart.MultipartFile;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
import java.util.UUID;

/**
 * @author origin
 */
@Slf4j
public abstract class FileUploader {

    @Autowired
    protected FileInfoService fileInfoService;

    @Autowired
    private FileUploadTaskService fileUploadTaskService;

    @Autowired
    private FileSliceUploadManager fileSliceUploadManager;

    @Autowired
    private ImageInfoService imageInfoService;

    @Autowired
    private RedisLock redisLock;

    private static final String FILE_UPLOAD_CHECK = "uploader:file_upload_check:";

    protected abstract StorageType getStorageType();

    /**
     * 快速上传
     *
     * @param identifier md5 唯一标识
     * @return fileId
     */
    public String quickUpload(String identifier) {
        return fileInfoService.getFileIdByIdentifier(identifier);
    }

    /**
     * 普通上传
     *
     * @param file 上传的文件
     * @return 文件列表
     */
    public String upload(MultipartFile file) {
        // todo 计算文件 md5
        String identifier = UUID.randomUUID().toString();
        String fileId = fileInfoService.getFileIdByIdentifier(identifier);
        if (CharSequenceUtil.isNotBlank(fileId)) {
            return fileId;
        }
        // todo 插入到 file_info 中, 再上传文件
        FileSliceUpload uploadFile = new FileSliceUpload();
        uploadFile.setChunkNumber(1);
        uploadFile.setChunkSize(0);
        uploadFile.setTotalChunks(1);
        uploadFile.setIdentifier(identifier);

        return fileId;
    }

    public void sliceUpload(MultipartFile file, FileSliceUpload fileSliceUpload) {
        String identifier = fileSliceUpload.getIdentifier();
        int chunkNumber = fileSliceUpload.getChunkNumber();
        if (fileSliceUploadManager.checkChunkUploaded(identifier, chunkNumber)) {
            log.info("分片已经上传过了, 跳过. identifier: {}, chunkNumber: {}", identifier, chunkNumber);
        } else {
            QiwenMultipartFile qiwenFile = new QiwenMultipartFile(file);
            // 检查文件是否有上传过
            checkFileUploaded(fileSliceUpload, qiwenFile);
            // 分片上传记录添加到 redis 中
            fileSliceUploadManager.storeChunkNumber(identifier, chunkNumber);
            // 上传分片
            if (doSliceUpload(qiwenFile, fileSliceUpload)) {
                if (checkFinish(fileSliceUpload)) {
                    finishSliceUpload(fileSliceUpload);
                }
            } else {
                // todo 上传失败, 重试
                log.error("文件上传失败, identifier: {}", identifier);
            }
        }
    }


    /**
     * 取消上传
     *
     * @param uploadFile 分片上传参数
     */
    public abstract void cancelUpload(FileSliceUpload uploadFile);

    private void checkFileUploaded(FileSliceUpload fileSliceUpload, QiwenMultipartFile qiwenFile) {
        String identifier = fileSliceUpload.getIdentifier();
        if (!fileSliceUploadManager.checkFileUploaded(identifier)) {
            String key = FILE_UPLOAD_CHECK + identifier;
            if (redisLock.tryLock(key)) {
                try {
                    if (!fileSliceUploadManager.checkFileUploaded(identifier)) {
                        log.info("identifier: {} 首次上传", identifier);
                        // 首次上传, file_info、file_upload_task 添加记录
                        FileInfo fileInfo = FileInfo.init(identifier, qiwenFile.getFileUrl(), qiwenFile.getSize(), getStorageType());
                        // todo 事务
                        fileInfoService.insert(fileInfo);
                        fileUploadTaskService.insertUploadTask(FileUploadTask.init(fileInfo.getFileId(), fileSliceUpload.getTotalChunks()));
                    }
                } finally {
                    redisLock.unlock(key);
                }
            }
        }
    }

    /**
     * 分片上传完成
     */
    private void finishSliceUpload(FileSliceUpload fileSliceUpload) {
        log.info("分片上传完成, identifier: {}", fileSliceUpload.getIdentifier());
        String identifier = fileSliceUpload.getIdentifier();

        CompleteSliceUploadResp fileUploadResult = doFinishSliceUpload(identifier);
        fileSliceUploadManager.clearCache(identifier);

        if (fileUploadResult.getBufferedImage() != null) {
            BufferedImage src = fileUploadResult.getBufferedImage();
            ImageInfo image = new ImageInfo();
            image.setImageWidth(src.getWidth());
            image.setImageHeight(src.getHeight());
            image.setFileId(fileInfoService.getFileIdByIdentifier(identifier));
            imageInfoService.insert(image);
        }
    }

    /**
     * @return true - 分片上传成功, false - 分片上传失败
     */
    protected abstract boolean doSliceUpload(QiwenMultipartFile qiwenFile, FileSliceUpload fileSliceUpload);

    protected abstract CompleteSliceUploadResp doFinishSliceUpload(String identifier);

    protected abstract CompleteSliceUploadResp organizationalResults(QiwenMultipartFile qiwenMultipartFile, FileSliceUpload uploadFile);

    protected abstract boolean checkFinish(FileSliceUpload fileSliceUpload);

    public synchronized boolean checkUploadStatus(FileSliceUpload uploadFile, File confFile) throws IOException {
        try (RandomAccessFile confAccessFile = new RandomAccessFile(confFile, "rw")) {
            // 设置文件长度
            confAccessFile.setLength(uploadFile.getTotalChunks());
            // 设置起始偏移量
            confAccessFile.seek(uploadFile.getChunkNumber() - 1);
            // 将指定的一个字节写入文件中 127，
            confAccessFile.write(Byte.MAX_VALUE);
        }
        byte[] completeStatusList = FileUtils.readFileToByteArray(confFile);
        // 创建 con f文件文件长度为总分片数，每上传一个分块即向 conf 文件中写入一个 127，那么没上传的位置就是默认的 0, 已上传的就是 127
        for (byte b : completeStatusList) {
            if (b != Byte.MAX_VALUE) {
                return false;
            }
        }
        FileUtil.del(confFile);
        return true;
    }

    public void writeByteDataToFile(byte[] fileData, File file, FileSliceUpload uploadFile) {
        //第一步 打开将要写入的文件
        RandomAccessFile raf;
        try {
            raf = new RandomAccessFile(file, "rw");
            //第二步 打开通道
            FileChannel fileChannel = raf.getChannel();
            //第三步 计算偏移量
            long position = (uploadFile.getChunkNumber() - 1) * uploadFile.getChunkSize();
            //第四步 获取分片数据
//            byte[] fileData = qiwenMultipartFile.getUploadBytes();
            //第五步 写入数据
            fileChannel.position(position);
            fileChannel.write(ByteBuffer.wrap(fileData));
            fileChannel.force(true);
            fileChannel.close();
            raf.close();
        } catch (IOException e) {
            throw new UploadException(e);
        }

    }

    //    /**
//     * 分片上传
//     *
//     * @param multipartFile 上传的文件
//     * @param uploadFile    分片上传参数
//     * @return 文件列表
//     */
//    public FileUploadResult upload(MultipartFile multipartFile, FileSliceUpload uploadFile) {
//        try {
//            QiwenMultipartFile qiwenMultipartFile = new QiwenMultipartFile(multipartFile);
//            return doUpload(qiwenMultipartFile, uploadFile);
//        } catch (Exception e) {
//            throw new UploadException(e);
//        }
//    }
    //    protected abstract FileUploadResult doUpload(QiwenMultipartFile qiwenMultipartFile, FileSliceUpload uploadFile);

}
